/*
 *
 * C++ MaJiang for 血流红中
 *
 * TuyooGame, 2021
 * zhouxin@tuyoogame.com
 *
 */
#include <zlib.h>
#include "log.h"
#include "mj_cache.h"

#define PATH_SIZE 512

//初始化互斥锁...
MJCache::MJCache() {
    //key: MFIHash, value: mj_hu_result buffer
    _mj_hu_cache = map<uint32_t, string> {};
    //记录mfihash对应的输入buffer，备查
    _mj_hu_input = map<uint32_t, string> {};
    //key: hasTing run clock, value: MFIHash
    //map本身是有顺序的，利用这个特性保留最慢的MAX_CACHE_SIZE个结果
    _has_ting_clock = multimap<uint32_t, uint32_t, greater<uint32_t>> {}; 
    pthread_mutex_init(&_hu_cache_lock, NULL);
}

//线程安全处理cache，保持计算最耗时的MAX_CACHE_SIZE个结果
MJ_CACHE_RET 
MJCache::getFromCache(XueLiuHZCard *xc) {
    char zbuffer[OUTPUT_SIZE];
    uLong tlen, blen;
    char tmpstr[128];

    if(xc->state != READY) return CACHE_ERROR_INPUT;

    uint32_t mfihash = xc->dumpInput(false);

    //printf("MJCache:request-%s(hash:%d)\n", xc->input_buffer, mfihash);
    pthread_mutex_lock(&_hu_cache_lock);
    if(_mj_hu_cache.find(mfihash) != _mj_hu_cache.end()) {
        //直接命中
        string rs = _mj_hu_cache[mfihash];
        blen = rs.length();
        tlen = OUTPUT_SIZE;
        if(uncompress((Bytef *)xc->output_buffer, &tlen, (const Bytef *)rs.c_str(), blen) != Z_OK) {
            log_info("MJCache:hit...uncompress error!(mfihash=%u)\n", mfihash);
            pthread_mutex_unlock(&_hu_cache_lock);
            return CACHE_ERROR_UNCOMPRESS;
        }
        //tlen包含了结尾的0...
        xc->output_size = tlen - 1;
        //xc->output_buffer[tlen - 1] = 0;
        log_debug("MJCache:hit...(mfihash:%x)\n", mfihash);
        pthread_mutex_unlock(&_hu_cache_lock);
        return CACHE_HIT;
    } else {
        pthread_mutex_unlock(&_hu_cache_lock);
        uint32_t et = xc->hasTing();
        xc->dumpOutput();
        MJ_CACHE_RET need_insert;
        pthread_mutex_lock(&_hu_cache_lock);
        if(_has_ting_clock.size()<MAX_CACHE_SIZE) {
            //cache未满
            need_insert = CACHE_INSERT_NOTFULL;
        } else {
            auto iter = --_has_ting_clock.end();
            uint32_t min_et = iter->first;
            uint32_t min_hash = iter->second;
            if(et>min_et and mfihash!=min_hash) {
                //换掉一个计算较快的cache
                _has_ting_clock.erase(iter);
                _mj_hu_cache.erase(min_hash);
                _mj_hu_input.erase(min_hash);
                need_insert = CACHE_INSERT_FULL;
            } else {
                need_insert = CACHE_NEED_NOT_INSERT;
            }
        }
        if(need_insert) {
            _has_ting_clock.insert(make_pair(et, mfihash));
            _mj_hu_input[mfihash] = string(xc->input_buffer);

            //压缩xc->output_buffer，放入_mj_hu_cache...
            tlen = xc->output_size+1;
            blen = compressBound(tlen); 
            if(compress((Bytef *)zbuffer, &blen, (const Bytef*)xc->output_buffer, tlen) != Z_OK) {
                log_warn("MJCache:insert...compress fail...\n");
                pthread_mutex_unlock(&_hu_cache_lock);
                return CACHE_ERROR_COMPRESS;
            }
            _mj_hu_cache[mfihash] = string(zbuffer, blen); 

            if(need_insert==CACHE_INSERT_FULL) sprintf(tmpstr, "MJCache: erase ok,");
            if(need_insert==CACHE_INSERT_NOTFULL) sprintf(tmpstr, "MJCache: not full,");
            log_info("%sinsert.(et:%u,hash:%x),cache_size=%ld\n",
                   tmpstr, et, mfihash,  _mj_hu_cache.size());
            pthread_mutex_unlock(&_hu_cache_lock);
            return need_insert;
        } else {
            sprintf(tmpstr, "MJCache: need not insert.");
            log_info("%s [%s](et:%u,hash:%x),cache_size=%ld\n",
                   tmpstr, xc->input_buffer, et, mfihash,  _mj_hu_cache.size());
            pthread_mutex_unlock(&_hu_cache_lock);
            return CACHE_NEED_NOT_INSERT;
        }
    }
}

//实测1M个结果，平均2K大小
//占用2.13G内存...
void 
MJCache::testCacheMem() {
    uint32_t hash = 1;
    char result[2048];
    memset(result, 1, 2048);
    result[2047]=0;
    for(int i=0; i<MAX_CACHE_SIZE; i++) {
        _mj_hu_cache[hash+i] = string(result);
        _has_ting_clock.insert(make_pair(i+1, hash+i));
    }
}

void 
MJCache::dumpCache(const char *path, bool isbin) {
    char zbuffer[OUTPUT_SIZE];
    uLong tlen, blen;

    pthread_mutex_lock(&_hu_cache_lock);
    char pat[PATH_SIZE], pat2[PATH_SIZE], pat3[PATH_SIZE];
    int count = 0;
    snprintf(pat, PATH_SIZE, "%s/cache_hu_result.txt", path);
    snprintf(pat2, PATH_SIZE, "%s/cache_ting_clock.txt", path);
    snprintf(pat3, PATH_SIZE, "%s/cache_hu_input.txt", path);

    ofstream fhr(pat, ios::binary);
    for(auto hr : _mj_hu_cache) {
        string rs = hr.second;
        blen = rs.length();
        if(!isbin) {
            tlen = OUTPUT_SIZE;
            if(uncompress((Bytef *)zbuffer, &tlen, (const Bytef *)rs.c_str(), blen) != Z_OK) {
                log_warn("MJCache:dump_cache uncompress error!(mfihash=%u)\n", hr.first);
                continue;
            }
            fhr << hr.first << " " << zbuffer<< endl;
        } else {
            fhr.write((char *)&hr.first, 4);
            fhr.write((char *)&blen, sizeof(blen));
            fhr.write(rs.c_str(), blen);
        }
        count++;
    }
    log_info("MJCache:dump %d hu_result..", count);
    fhr.close();

    count = 0;
    ofstream fhi(pat3);
    for(auto tc : _mj_hu_input) {
        fhi << tc.first << " " << tc.second << endl;
        count++;
    }
    log_info_n("dump %d hu_input..", count);
    fhi.close();

    count = 0;
    ofstream ftc(pat2);
    for(auto tc : _has_ting_clock) {
        ftc << tc.first << " " << tc.second << endl;
        count++;
    }
    log_info_n("dump %d ting_clock..\n", count);
    ftc.close();

    pthread_mutex_unlock(&_hu_cache_lock);
}

void 
MJCache::loadCache(const char *path, bool isbin) {
    char zbuffer[OUTPUT_SIZE];
    uLong tlen, blen;

    pthread_mutex_lock(&_hu_cache_lock);
    char pat[PATH_SIZE], pat2[PATH_SIZE], pat3[PATH_SIZE];
    int count = 0;
    snprintf(pat, PATH_SIZE, "%s/cache_hu_result.txt", path);
    snprintf(pat2, PATH_SIZE, "%s/cache_ting_clock.txt", path);
    snprintf(pat3, PATH_SIZE, "%s/cache_hu_input.txt", path);

    ifstream fhr(pat, ios::binary);
    if(!fhr.is_open()) {
        log_info_n("OK!\n");
        pthread_mutex_unlock(&_hu_cache_lock);
        return;
    }
    string s1, s2;
    if(!isbin) {
        while(fhr >> s1 >> s2) {
            uint32_t hash = stol(s1);
            //压缩xc->output_buffer，放入_mj_hu_cache...
            tlen = s2.length() + 1;
            blen = compressBound(tlen); 
            if(compress((Bytef *)zbuffer, &blen, (const Bytef*)s2.c_str(), tlen) != Z_OK) {
                log_warn("MJCache:load_cache...compress fail...");
                continue;
            }
            _mj_hu_cache[hash] = string(zbuffer, blen);
            count++;
            if(count % 20000 == 0) log_info_n(".");
        }
    } else {
        while(fhr.peek()!=EOF) {
            uint32_t hash;
            uLong blen; 
            fhr.read((char *)&hash, 4);
            fhr.read((char *)&blen, sizeof(blen));
            s2.resize(blen);
            fhr.read((char *)s2.c_str(), blen);
            _mj_hu_cache[hash] = s2;
            count++;
            if(count % 10000 == 0) log_info_n(".");
        }
    }
    log_info_n("OK!\n");
    log_info("MJCache:load %d hu_result..", count);
    fhr.close();

    count = 0;
    ifstream fhi(pat3);
    string ss1, ss2;
    while(fhi >> ss1 >> ss2) {
        uint32_t hash = stol(ss1);
        _mj_hu_input[hash] = ss2;
        count++;
    }
    log_info_n("load %d hu_input..", count);
    fhi.close();

    count = 0;
    ifstream ftc(pat2);
    uint32_t i1, i2;
    while(ftc >> i1 >> i2) {
        _has_ting_clock.insert(make_pair(i1, i2));
        count++;
    }
    log_info_n("load %d ting_clock..\n", count);
    ftc.close();

    pthread_mutex_unlock(&_hu_cache_lock);
}
